package gl.drawer

import gl.util.GLUtils
import gl.window
import org.joml.Matrix4f
import org.lwjgl.glfw.GLFW
import org.lwjgl.opengl.GL45C.*
import kotlin.random.Random

class PointSprites: Drawer() {

    var program = 0
    var uniMv = 0
    var uniMvp = 0
    var uniColor1 = 0
    var uniColor2 = 0

    var vertexBuffer = 0
    var vao = 0
    var pointCount = 0

    override fun initialize() {
        // program
        program = glCreateProgram()
        val vs = glCreateShader(GL_VERTEX_SHADER)
        glShaderSource(vs, vertexShader)
        glCompileShader(vs)
        check(glGetShaderi(vs, GL_COMPILE_STATUS) == GL_TRUE) { glGetShaderInfoLog(vs) }
        val fs = glCreateShader(GL_FRAGMENT_SHADER)
        glShaderSource(fs, fragmentShader)
        glCompileShader(fs)
        check(glGetShaderi(fs, GL_COMPILE_STATUS) == GL_TRUE) { glGetShaderInfoLog(fs) }
        glAttachShader(program, vs)
        glAttachShader(program, fs)
        glLinkProgram(program)
        glDetachShader(program, vs)
        glDetachShader(program, fs)
        glDeleteShader(vs)
        glDeleteShader(fs)
        check(glGetProgrami(program, GL_LINK_STATUS) == GL_TRUE) { glGetProgramInfoLog(program) }
        glUseProgram(program)
        uniMv = glGetUniformLocation(program, "mv")
        uniMvp = glGetUniformLocation(program, "mvp")
        uniColor1 = glGetUniformLocation(program, "color1")
        uniColor2 = glGetUniformLocation(program, "color2")
        glUniform4fv(uniColor1, floatArrayOf(0.6f, 0.0f, 0.0f, 1.0f))
        glUniform4fv(uniColor2, floatArrayOf(0.9f, 0.7f, 1.0f, 0.0f))
        // vertex
        vertexBuffer = glGenBuffers()
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer)
        pointCount = 50 + Random.nextInt(50)
        val points = FloatArray(pointCount) {
            if (it % 4 == 3) {
                1f // z
            } else {
                // xyz: -1, 1
                Random.nextFloat() * 2 - 1
            }
        }
        glBufferStorage(GL_ARRAY_BUFFER, points, 0)
        vao = glGenVertexArrays()
        glBindVertexArray(vao)
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0)

        // 需要在着色器中修改点大小
        glEnable(GL_PROGRAM_POINT_SIZE)
        glEnable(GL_BLEND)
        glBlendFunc(GL_ONE, GL_ONE);
        glEnable(GL_CULL_FACE);
        glDisable(GL_DEPTH_TEST);

        // 初始化视椎体
        val aspect = window.height / window.width.toFloat()
        val mat = FloatArray(16)
        Matrix4f().identity().frustum(-1f, 1f, -aspect, aspect, 1f, 8f)[mat]
        glUniformMatrix4fv(uniMvp, false, mat)

        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer)
    }

    override fun update(): Boolean {
        super.update()
        val t = (GLFW.glfwGetTime().toInt() and 0x1FFF).toFloat() / 0x1FFF.toFloat()
        val mat = FloatArray(16)
        Matrix4f().identity().translate(0f, 0f, -2f)
            .rotateY(t * 360f)
            .rotateZ(t * 720f)[mat]
        glUniformMatrix4fv(uniMv, false, mat)
        glDrawArrays(GL_POINTS, 0, pointCount)
        GLUtils.capture()
        return false
    }

    override fun terminal() {
        glDisable(GL_PROGRAM_POINT_SIZE)
        // vertex
        glBindVertexArray(0)
        glDeleteVertexArrays(0)
        glDeleteBuffers(vertexBuffer)
        // program
        glUseProgram(0)
        glDeleteProgram(program)
    }

    override fun onResize(width: Int, height: Int) {
        if (glGetInteger(GL_CURRENT_PROGRAM) == program) {
            val aspect = height / width.toFloat()
            val mat = FloatArray(16)
            Matrix4f().identity().frustum(-1f, 1f, -aspect, aspect, 1f, 8f)[mat]
            glUniformMatrix4fv(uniMvp, false, mat)
        }
    }
}

private val vertexShader = """
#version 400 core

uniform mat4 mv;
uniform mat4 mvp;

layout(location=0) in vec4 vPosition;

void main() {
    vec4 pos = mvp * (mv * vPosition);
    // 根据透视重新调整点大小
    gl_PointSize = (1.0 - pos.z / pos.w) * 32;
    gl_Position = pos;
}
""".trimIndent()

private val fragmentShader = """
#version 400 core

uniform vec4 color1;
uniform vec4 color2;

out vec4 color;

// 片元着色器
void main() {
    vec2 temp = gl_PointCoord - vec2(0.5, 0.5);
    float distance = dot(temp, temp);
    if (distance > 0.25) {
        // r=0.5, 超过该半径的片元直接丢弃
        discard;
    }
    
    if (distance < 0.16) {
        // r=0.4, 小于此半径直接丢弃
        discard;
    }
    
    color = mix(color1, color2, smoothstep(0.1, 0.25, distance));
}
""".trimIndent()